package com.ruoyi.framework.web.service;

import javax.annotation.Resource;

import com.alibaba.fastjson.JSON;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.dto.WeChatLoginDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.BlackListException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 登录校验方法
 * 
 * @author ruoyi
 */
@Component
public class SysLoginService
{
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;
    
    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

    /**
     * 校验验证码
     * 
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid)
    {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled)
        {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
            String captcha = redisCache.getCacheObject(verifyKey);
            if (captcha == null)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            redisCache.deleteObject(verifyKey);
            if (!code.equalsIgnoreCase(captcha))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }

    /**
     * 登录前置校验
     * @param username 用户名
     * @param password 用户密码
     */
    public void loginPreCheck(String username, String password)
    {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    /**
     * 微信一键登录
     * @param dto
     * @return
     */
    public Map<String, Object> weChatLogin(WeChatLoginDto dto) {
        // 获取用户openId
        com.ruoyi.framework.web.domain.vo.WeChatLoginVo weChatLoginVo = this.weChatLogin(dto.getCode());
        SysUser sysUser = new SysUser();
        sysUser.setOpenId(weChatLoginVo.getOpenId());
        sysUser.setUserType("01");
        // 如果不存在新增用户信息,否则放行
        List<SysUser> sysUsers = userService.selectUserList(sysUser);
        String password = "123456";
        if (CollectionUtils.isEmpty(sysUsers)){
            String phoneNumber = this.getPhoneNumber(dto.getPhoneCode());
            sysUser.setUserName(phoneNumber);
            sysUser.setNickName(generateFourCharName());
            sysUser.setPhonenumber(phoneNumber);
            sysUser.setPassword(SecurityUtils.encryptPassword(password));
            sysUser.setStatus("0");
            sysUser.setDelFlag("0");
            userService.insertUser(sysUser);
            sysUser.setPassword(password);
        }else {
            sysUser.setPhonenumber(sysUsers.get(0).getPhonenumber());
        }
        String token = this.login(sysUser.getPhonenumber(), password, "", "");
        Map<String, Object> map = new HashMap<>();
        map.put("token",token);
        return map;
    }

    /**
     * 常见复姓列表
     */
    private static final String[] COMPOUND_SURNAMES = {
            "欧阳", "上官", "皇甫", "司徒", "司马",
            "诸葛", "宇文", "长孙", "慕容", "东方",
            "尉迟", "万俟", "闻人", "夏侯", "赫连"
    };

    /**
     * 常用名字字符集
     */
    private static final String[] NAME_CHARACTERS = {
            "子", "浩", "思", "伟", "明", "天", "宇", "嘉", "俊", "欣",
            "雨", "晨", "博", "文", "雅", "婷", "瑞", "哲", "悦", "慧",
            "杰", "琳", "宁", "琪", "睿", "舒", "翔", "彦", "怡", "宜"
    };

    public static String generateFourCharName() {
        Random random = new Random();

        // 随机选择复姓
        String surname = COMPOUND_SURNAMES[random.nextInt(COMPOUND_SURNAMES.length)];

        // 随机生成名字部分（两个字）
        String givenName = NAME_CHARACTERS[random.nextInt(NAME_CHARACTERS.length)]
                + NAME_CHARACTERS[random.nextInt(NAME_CHARACTERS.length)];
        return surname + givenName;
    }

    public com.ruoyi.framework.web.domain.vo.WeChatLoginVo weChatLogin(String loginCode) {
        HashMap<String, String> map = new HashMap(8);
        map.put("appid", "wx159bb0413d5de717");
        map.put("secret", "0fee41c3d0a02e6b10b670cce58135d2");
        map.put("js_code", loginCode);
        map.put("grant_type", "authorization_code");
        String wxDataJson = this.getFromRequestParam(map);
        com.ruoyi.framework.web.domain.vo.WeChatLoginVo vo =  JSON.parseObject(wxDataJson, com.ruoyi.framework.web.domain.vo.WeChatLoginVo.class);
        if (org.apache.commons.lang3.StringUtils.isBlank(vo.getOpenId())) {

            throw new RuntimeException("获取openId失败");
        } else {
            return vo;
        }
    }

    public String getPhoneNumber(String phoneCode) {
        HashMap<String, String> map = new HashMap(2);
        map.put("code", phoneCode);
        String accessToken = this.getStableToken();
        String phoneNumberJson = this.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken, JSON.toJSONString(map));
        com.ruoyi.framework.web.domain.vo.WeChatUserPhoneVo vo = JSON.parseObject(phoneNumberJson, com.ruoyi.framework.web.domain.vo.WeChatUserPhoneVo.class);
        if (org.apache.commons.lang3.StringUtils.isBlank(vo.getPhoneInfo().getPurePhoneNumber())) {

            throw new RuntimeException("获取微信用户手机号失败");
        } else {
            return vo.getPhoneInfo().getPhoneNumber();
        }
    }
    private String getFromRequestParam(Map<String, String> params) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("https://api.weixin.qq.com/sns/jscode2session");

        for(Map.Entry<String, String> entry : params.entrySet()) {
            builder.queryParam((String)entry.getKey(), new Object[]{entry.getValue()});
        }

        String builtUrl = builder.build().toUriString();
        ResponseEntity<String> response = this.restTemplate.exchange(builtUrl, org.springframework.http.HttpMethod.GET, HttpEntity.EMPTY, String.class, new Object[0]);
        return (String)response.getBody();
    }

    private <T> String post(String url, T request) {
        return (String)this.restTemplate.postForObject(url, request, String.class, new Object[0]);
    }

    private String getStableToken() {
        String stableToken = (String)this.stringRedisTemplate.opsForValue().get("we_chat_stable_token");
        if (org.apache.commons.lang3.StringUtils.isNotBlank(stableToken)) {
            return stableToken;
        } else {
            HashMap<String, String> map = new HashMap(8);
            map.put("appid", "wx159bb0413d5de717");
            map.put("secret", "0fee41c3d0a02e6b10b670cce58135d2");
            map.put("grant_type", "client_credential");
            String weChatStableTokenJson = this.post("https://api.weixin.qq.com/cgi-bin/stable_token", map);
            com.ruoyi.framework.web.domain.vo.WeChatStableTokenVo vo =JSON.parseObject(weChatStableTokenJson, com.ruoyi.framework.web.domain.vo.WeChatStableTokenVo.class);
            if (org.apache.commons.lang3.StringUtils.isBlank(vo.getAccessToken())) {
                throw new RuntimeException("获取接口调用凭证失败");
            } else {
                this.stringRedisTemplate.opsForValue().set("we_chat_stable_token", vo.getAccessToken(), Long.parseLong(vo.getExpiresIn()) - 300L, TimeUnit.SECONDS);
                return vo.getAccessToken();
            }
        }
    }
}
